package fr.utbm.gl52.smartgrid.view.mainpanel;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.LineBorder;

import fr.utbm.gl52.smartgrid.util.BiControllable;
import fr.utbm.gl52.smartgrid.util.Constants;
import fr.utbm.gl52.smartgrid.util.PropertiesBuffer;
import fr.utbm.gl52.smartgrid.util.properties.Properties;
import fr.utbm.gl52.smartgrid.util.properties.PropertyDouble;
import fr.utbm.gl52.smartgrid.util.properties.PropertyInt;
import fr.utbm.gl52.smartgrid.util.properties.PropertyLong;
import fr.utbm.gl52.smartgrid.util.properties.PropertyString;
import fr.utbm.gl52.smartgrid.view.Messages;

/**
 * <b>Property Editor Panel</b><br/>
 * Creates a panel with properties it has been passed, placed in Swing
 * components adapted to their types. <br/>
 * Values are placed in components adapted to their types, so they are not
 * supposed to generate errors/wrong values.
 * 
 * <br/>
 * <br/>
 * <b>How to use:</b><br/>
 * Assuming you have declared a <code>JFrame</code> called <code>frame</code>, <br/>
 * 
 * <ol>
 * <li>
 * Create a map of Properties: <br/>
 * <code>TreeMap&lt;String, Properties&lt;?&gt;&gt; properties = TreeMap&lt;String, Properties&lt;?&gt;&gt;();</code>
 * </li>
 * <li>
 * Fill this map with <code>Properties</code>: <br/>
 * <code>properties.put("Property int", new PropertyInt("Property int", 52, false)); <br/>
 * 		PropertyString p = new PropertyString("property", "value", false); <br/>
 * 		properties.put(p.getName(), p);</code></li>
 * <li>
 * Instantiate a PropertyEditorPanel and add it to your frame: <br/>
 * <code>PropertyEditorPanel panel = new PropertyEditorPanel(properties, true); <br/>
 * 		frame.add(panel);</code></li>
 * </ol>
 * 
 * ====================================<br>
 * <b>Important note:</b><br/>
 * In order to be able to properly use the PropertyEditorPanel, you will need to
 * add the jcalendar-1.4.jar library to your class path. In Eclipse, right-click
 * on the project, open the Properties > Java Build Path > Libraries > Add
 * external JARs and point to the file that was provided in SVN, in the
 * <i>model</i> directory. Then, refresh the project.
 * 
 * @author maxime.oger@utbm.fr
 * @author francois.laithier@utbm.fr
 * 
 * @version 2.0
 * 
 * @see Example_PropertyEditorPanel.java
 */
public class PropertyEditorPanel extends JPanel implements BiControllable,
		ActionListener {

	private static final long serialVersionUID = 6306943720073311500L;
	private TreeMap<String, JComponent> components;
	private Map<String, Properties<?>> properties;
	private boolean editable;
	private JButton buttonOK;

	/**
	 * Empty constructor
	 * 
	 * @deprecated
	 */
	public PropertyEditorPanel() {
		super(new GridLayout(0, 2, 0, 10));
		this.properties = new TreeMap<String, Properties<?>>();
		this.components = new TreeMap<String, JComponent>();
		this.editable = true;
		this.buttonOK = new JButton(Messages.getString("PropertyEditorPanel.0")); //$NON-NLS-1$
	}

	/**
	 * Creates a new PropertyEditorPanel.
	 * 
	 * @param properties
	 *            List of properties
	 * @param editable
	 *            Whether the panel is editable or not
	 */
	public PropertyEditorPanel(Map<String, Properties<?>> properties,
			UUID uuid, boolean editable) {
		super(new GridLayout(0, 2, 0, 10));
		this.properties = properties;
		this.editable = editable;
		this.components = new TreeMap<String, JComponent>();
		this.buttonOK = new JButton(Messages.getString("PropertyEditorPanel.0")); //$NON-NLS-1$
		this.buttonOK.setActionCommand(Constants.PREFIX_VALIDATEPPEP + uuid);
		PropertiesBuffer.setDeviceID(uuid);
		printPanel();
	}

	/**
	 * Sets a map of properties
	 * 
	 * @param properties
	 */
	public void setProperties(Map<String, Properties<?>> properties) {
		this.properties = properties;
		printPanel();
	}

	/**
	 * 
	 * @param editable
	 */
	public void setEditable(boolean editable) {
		this.editable = editable;
		printPanel();
	}

	/**
	 * 
	 * @return
	 */
	public boolean isEditable() {
		return this.editable;
	}

	/**
	 * Returns a property from the editor panel
	 * 
	 * @param propertyName
	 *            Name of the property
	 * @return Property or null if the given name does not match any property.
	 */
	public Properties<?> getProperty(String propertyName) {
		Properties<?> p = null;

		if (this.components.containsKey(propertyName)
				&& this.properties.containsKey(propertyName)) {
			p = this.properties.get(propertyName);

			Class<?> type = p.getType();
			if (type == Integer.class) {
				int value = (Integer) ((JSpinner) this.components.get(propertyName))
						.getValue();
				((PropertyInt) p).setValue(value);
			}

			// Strings
			else if (type == String.class) {
				String value = ((JTextField) this.components.get(propertyName))
						.getText();
				((PropertyString) p).setValue(value);
			}
			// else if (type == Double.class) {
			// Double value = (Double) ((JSpinner)
			// components.get(propertyName)).getValue();
			// ((PropertyDouble) p).setValue(value);
			// }
			else if (type == Long.class) {
				Long value = (Long) ((JSpinner) this.components.get(propertyName))
						.getValue();
				((PropertyLong) p).setValue(value);
			}

		}

		return p;
	}

	/**
	 * Returns the current values, in a map.
	 * 
	 * @return
	 */
	public Map<String, Properties<?>> getAllProperties() {
		// Copy the current properties into a new map
		TreeMap<String, Properties<?>> m = new TreeMap<String, Properties<?>>();
		m.putAll(this.properties);

		Iterator<Entry<String, Properties<?>>> it = m.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, Properties<?>> pairs = it.next();
			Properties<?> prop = pairs.getValue();

			Class<?> type = m.get(prop.getName()).getType();
			if (type == Integer.class) {
				PropertyInt p = (PropertyInt) this.properties.get(prop.getName());
				int value = (Integer) ((JSpinner) this.components
						.get(prop.getName())).getValue();
				p.setValue(value);
			} else if (type == String.class) {
				PropertyString p = (PropertyString) this.properties.get(prop
						.getName());
				String value = ((JTextField) this.components.get(prop.getName()))
						.getText();
				p.setValue(value);
			} else if (type == Double.class) {
				PropertyDouble p = (PropertyDouble) this.properties.get(prop
						.getName());
				String value = (String) ((JSpinner) this.components.get(prop
						.getName())).getValue().toString();
				Double v = Double.parseDouble(value);

				p.setValue(v);
			} else if (type == Long.class) {
				PropertyLong p = (PropertyLong) this.properties.get(prop.getName());
				String value = ((JSpinner) this.components.get(prop.getName()))
						.getValue().toString();
				Long v = Long.parseLong(value);
				p.setValue(v);
			}
		}
		return m;
	}

	/**
	 * Creates the components to be printed, using the property list the current
	 * object possesses.
	 */
	private void printPanel() {

		// Remove all previous components on the view
		this.removeAll();
		this.components.clear();

		// Iterate through the map of properties and build a component according
		// to the data type
		Iterator<Entry<String, Properties<?>>> it = this.properties.entrySet()
				.iterator();
		while (it.hasNext()) {
			Entry<String, Properties<?>> pairs = it.next();
			Properties<?> p = pairs.getValue();

			add(new JLabel(p.getName()));

			/*
			 * Create input according to the property's type
			 */
			JComponent component = null;

			// Integers
			Class<?> type = p.getType();
			if (type == Integer.class) {
				component = (JSpinner) new JSpinner();
				((JSpinner) component).setValue(p.getValue());
			}

			// Strings
			else if (type == String.class) {
				component = new JTextField("" + p.getValue()); //$NON-NLS-1$
			} else if (type == Double.class) {
				SpinnerModel model = new SpinnerNumberModel(0.0, null, null,
						0.1);
				component = (JSpinner) new JSpinner(model);
				((JSpinner) component).setValue(p.getValue());
			} else if (type == Long.class) {
				component = (JSpinner) new JSpinner();
				((JSpinner) component).setValue(p.getValue());
			}

			if (component != null) {
				// Check errors
				if (p.hasError()) {
					component.setBorder(new LineBorder(Color.RED, 2));
				}

				// Edition
				component.setEnabled(this.editable);

				// Add the component to the panel
				add(component);

				this.components.put(p.getName(), component);
			}
		}
		add(this.buttonOK);

	}

	@Override
	public void addController(ActionListener controller) {

		this.buttonOK.addActionListener(controller);
		this.buttonOK.addActionListener(this);
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		TreeMap<String, Properties<?>> props = (TreeMap<String, Properties<?>>) getAllProperties();
		PropertiesBuffer.setProperties(props);
	}

	@Override
	public void addControllers(ActionListener internalController,
			ActionListener externalController) {
		this.buttonOK.addActionListener(internalController);
		this.buttonOK.addActionListener(externalController);
		this.buttonOK.addActionListener(this);
	}
}
